#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
数据处理测试脚本
按照project_outline.md中描述的四个数据处理阶段逐步实现
并为每个流程的数据处理结果生成可视化文件
"""

import os
import sys
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
from matplotlib.font_manager import FontProperties
import logging

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

# 导入自定义模块
try:
    from src.excel_handler.precise_merged_cell_detector import PreciseMergedCellDetector
    from src.utils.logger import setup_logger
    logger = setup_logger('data_processing_test')
    logger.info("已成功导入自定义模块")
except Exception as e:
    print(f"导入模块失败: {str(e)}")
    # 使用内置logging作为备选
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    logger = logging.getLogger('data_processing_test')
    logger.error(f"导入自定义模块失败，使用内置logging: {str(e)}")

# 配置路径
TEST_RESULTS_DIR = os.path.join(os.path.dirname(__file__), 'test_results', 'processed_data')
VISUALIZATIONS_DIR = os.path.join(os.path.dirname(__file__), 'test_results', 'visualizations')
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Excel源文件
TASK_EXCEL_FILE = os.path.join(ROOT_DIR, '瀚的发货用包装箱任务.xlsx')
HISTORY_EXCEL_FILE = os.path.join(ROOT_DIR, '瀚越已发货包装数据总表.xlsx')

class DataProcessingTester:
    """
    数据处理测试类，按照四个阶段实现数据处理流程
    """
    
    def __init__(self):
        """
        初始化测试器
        """
        self.detector = None
        self.task_data = None  # 任务数据
        self.history_data = None  # 历史包装数据
        self.processed_data = {}  # 存储各阶段处理后的数据
        
        try:
            # 初始化合并单元格检测器
            self.detector = PreciseMergedCellDetector()
            logger.info("初始化PreciseMergedCellDetector成功")
            
            # 确保目录存在
            self.ensure_directory(TEST_RESULTS_DIR)
            self.ensure_directory(VISUALIZATIONS_DIR)
            
        except Exception as e:
            logger.error(f"初始化失败: {str(e)}")
            raise
    
    def ensure_directory(self, directory):
        """
        确保目录存在
        """
        try:
            os.makedirs(directory, exist_ok=True)
            logger.info(f"确保目录存在: {directory}")
            return True
        except Exception as e:
            logger.error(f"创建目录失败: {str(e)}")
            print(f"创建目录失败: {str(e)}")
            return False
    
    # =============== 1. 前处理阶段 ===============
    def pre_processing_phase(self):
        """
        前处理阶段：文件选取、数据导入、数据格式化、数据统计
        """
        print("\n=== 1. 前处理阶段 ===")
        logger.info("开始前处理阶段")
        
        try:
            # 1.1 文件选取
            print("\n1.1 文件选取")
            if not os.path.exists(TASK_EXCEL_FILE):
                logger.error(f"任务数据文件不存在: {TASK_EXCEL_FILE}")
                print(f"✗ 任务数据文件不存在: {TASK_EXCEL_FILE}")
                return False
            
            if not os.path.exists(HISTORY_EXCEL_FILE):
                logger.error(f"历史包装数据文件不存在: {HISTORY_EXCEL_FILE}")
                print(f"✗ 历史包装数据文件不存在: {HISTORY_EXCEL_FILE}")
                return False
            
            print(f"✓ 任务数据文件: {os.path.basename(TASK_EXCEL_FILE)}")
            print(f"✓ 历史包装数据文件: {os.path.basename(HISTORY_EXCEL_FILE)}")
            
            # 1.2 数据导入
            print("\n1.2 数据导入")
            self.task_data = pd.read_excel(TASK_EXCEL_FILE)
            self.history_data = pd.read_excel(HISTORY_EXCEL_FILE)
            
            logger.info(f"成功导入任务数据，共{len(self.task_data)}行")
            logger.info(f"成功导入历史包装数据，共{len(self.history_data)}行")
            print(f"✓ 成功导入任务数据，共{len(self.task_data)}行")
            print(f"✓ 成功导入历史包装数据，共{len(self.history_data)}行")
            
            # 1.3 数据格式化
            print("\n1.3 数据格式化")
            self.task_data = self._format_data(self.task_data, "任务数据")
            self.history_data = self._format_data(self.history_data, "历史包装数据")
            
            # 1.4 数据统计
            print("\n1.4 数据统计")
            task_stats = self._statistical_analysis(self.task_data)
            
            # 保存前处理阶段结果
            self.processed_data['pre_processing'] = {
                'task_data': self.task_data.copy(),
                'history_data': self.history_data.copy(),
                'stats': task_stats
            }
            
            # 生成前处理阶段可视化
            self._generate_pre_processing_visualization(task_stats)
            
            # 保存前处理阶段数据
            self._save_processed_data(self.task_data, "1_1_任务数据_格式化.csv")
            self._save_processed_data(self.history_data, "1_2_历史包装数据_格式化.csv")
            
            logger.info("前处理阶段完成")
            print("\n✓ 前处理阶段完成")
            return True
            
        except Exception as e:
            logger.error(f"前处理阶段失败: {str(e)}")
            print(f"✗ 前处理阶段失败: {str(e)}")
            return False
    
    def _format_data(self, df, data_type):
        """
        格式化数据
        """
        try:
            formatted_df = df.copy()
            
            # 处理缺失值
            numeric_columns = formatted_df.select_dtypes(include=[np.number]).columns
            formatted_df[numeric_columns] = formatted_df[numeric_columns].fillna(0)
            
            string_columns = formatted_df.select_dtypes(include=['object']).columns
            formatted_df[string_columns] = formatted_df[string_columns].fillna('')
            
            # 去除字符串列的前后空格
            for col in string_columns:
                formatted_df[col] = formatted_df[col].astype(str).str.strip()
            
            # 将列名标准化（去除空格）
            formatted_df.columns = formatted_df.columns.str.strip()
            
            # 处理预完工日期格式（如果存在）
            if '预完工日期' in formatted_df.columns:
                # 先使用自定义函数提取括号前的日期部分
                formatted_df['预完工日期'] = formatted_df['预完工日期'].apply(self._format_delivery_date)
                # 然后尝试标准化日期格式为'YYYY-MM-DD'
                try:
                    # 使用errors='coerce'确保无法解析的日期会被转换为NaT
                    formatted_df['预完工日期'] = pd.to_datetime(formatted_df['预完工日期'], errors='coerce').dt.strftime('%Y-%m-%d')
                    # 对于无法解析的日期，保持原始格式
                    formatted_df['预完工日期'] = formatted_df['预完工日期'].fillna('')
                except Exception as e:
                    logger.warning(f"无法标准化{data_type}的日期格式: {str(e)}")
            
            logger.info(f"{data_type}格式化完成")
            print(f"  {data_type}格式化完成")
            return formatted_df
            
        except Exception as e:
            logger.error(f"格式化{data_type}失败: {str(e)}")
            raise
    
    def _statistical_analysis(self, df):
        """
        数据统计分析
        """
        try:
            stats = {}
            
            # 统计任务数据中产品种类
            if '规格型号' in df.columns:
                product_types = df['规格型号'].nunique()
                stats['product_types'] = product_types
                logger.info(f"任务数据中共有 {product_types} 种不同的产品规格型号")
                print(f"  任务数据中共有 {product_types} 种不同的产品规格型号")
            
            # 统计同客户同日期同产品的订单数量
            if all(col in df.columns for col in ['客户简称', '规格型号', '预完工日期', '数量']):
                # 按客户、产品、日期分组统计数量
                grouped = df.groupby(['客户简称', '规格型号', '预完工日期'])['数量'].sum().reset_index()
                stats['order_groups'] = len(grouped)
                stats['order_summary'] = grouped
                logger.info(f"任务数据中有 {len(grouped)} 组不同的客户-产品-日期组合")
                print(f"  任务数据中有 {len(grouped)} 组不同的客户-产品-日期组合")
            
            # 基本统计信息
            stats['total_rows'] = len(df)
            stats['columns'] = list(df.columns)
            stats['data_types'] = df.dtypes.astype(str).to_dict()
            
            return stats
            
        except Exception as e:
            logger.error(f"统计分析失败: {str(e)}")
            raise
    
    # =============== 2. 数据预处理阶段 ===============
    def data_preprocessing_phase(self):
        """
        数据预处理阶段：历史包装类型判定、数据合并、建立产品包装方案库、产品归类
        """
        print("\n=== 2. 数据预处理阶段 ===")
        logger.info("开始数据预处理阶段")
        
        try:
            # 检查前处理阶段数据是否存在
            if 'pre_processing' not in self.processed_data:
                print("✗ 请先完成前处理阶段")
                return False
            
            # 获取前处理阶段的数据
            history_data = self.processed_data['pre_processing']['history_data'].copy()
            task_data = self.processed_data['pre_processing']['task_data'].copy()
            
            # 2.1 历史包装类型判定
            print("\n2.1 历史包装类型判定")
            history_data = self._identify_packaging_types(history_data, HISTORY_EXCEL_FILE)
            
            # 2.2 数据合并
            print("\n2.2 数据合并")
            task_data = self._merge_task_data(task_data)
            
            # 2.3 建立产品包装方案库
            print("\n2.3 建立产品包装方案库")
            packaging_library, task_data = self._build_packaging_library(history_data, task_data)
            
            # 2.4 产品归类
            print("\n2.4 产品归类")
            task_data = self._classify_products(task_data, packaging_library)
            
            # 保存预处理阶段结果
            self.processed_data['preprocessing'] = {
                'history_data': history_data.copy(),
                'task_data': task_data.copy(),
                'packaging_library': packaging_library
            }
            
            # 生成预处理阶段可视化
            self._generate_preprocessing_visualization(history_data, task_data)
            
            # 保存预处理阶段数据
            self._save_processed_data(history_data, "2_1_历史数据包装类型判定.csv")
            self._save_processed_data(task_data, "2_2_任务数据_预处理.csv")
            
            logger.info("数据预处理阶段完成")
            print("\n✓ 数据预处理阶段完成")
            return True
            
        except Exception as e:
            logger.error(f"数据预处理阶段失败: {str(e)}")
            print(f"✗ 数据预处理阶段失败: {str(e)}")
            return False
    
    def _identify_packaging_types(self, df, excel_file_path):
        """
        基于合并单元格识别包装类型
        """
        try:
            # 使用detect_combinatorial_packaging方法识别包装类型
            result_df = self.detector.detect_combinatorial_packaging(df, excel_file_path)
            
            # 统计结果
            combined_count = len(result_df[result_df['包装类型'] == '组合包装'])
            individual_count = len(result_df) - combined_count
            
            logger.info(f"包装类型识别完成 - 组合包装: {combined_count}, 独立包装: {individual_count}")
            print(f"  组合包装: {combined_count} 条")
            print(f"  独立包装: {individual_count} 条")
            
            return result_df
            
        except Exception as e:
            logger.error(f"识别包装类型失败: {str(e)}")
            # 作为备选，尝试简单的包装类型判断
            try:
                print("  尝试备选方法识别包装类型")
                if '包装箱方案' in df.columns:
                    # 简单规则：如果包装箱方案包含分隔符，则视为组合包装
                    df['包装类型'] = df['包装箱方案'].apply(lambda x: '组合包装' if any(s in str(x) for s in ['/', '+', ',', ';']) else '独立包装')
                    return df
            except:
                pass
            raise
    
    def _merge_task_data(self, df):
        """
        合并任务数据
        """
        try:
            # 检查必要的列
            required_columns = ['客户简称', '规格型号', '预完工日期', '数量']
            if not all(col in df.columns for col in required_columns):
                missing_columns = [col for col in required_columns if col not in df.columns]
                logger.warning(f"任务数据缺少必要的列: {missing_columns}，跳过合并操作")
                print(f"  任务数据缺少必要的列: {missing_columns}，跳过合并操作")
                # 添加所需的字段
                for col in ['备注', '产品归类', '独立包装上限']:
                    if col not in df.columns:
                        df[col] = ''
                return df
            
            # 合并相同客户、产品和日期的行
            merged_df = df.copy()
            
            # 定义合并函数
            def merge_rows(group):
                if len(group) == 1:
                    return group.iloc[0]
                
                result = group.iloc[0].copy()
                result['数量'] = group['数量'].sum()
                
                # 合并客户订单号和行号
                if '客户订单号' in group.columns:
                    result['客户订单号'] = '/'.join(str(x) for x in group['客户订单号'].unique() if str(x).strip())
                if '行号' in group.columns:
                    result['行号'] = '/'.join(str(x) for x in group['行号'].unique() if str(x).strip())
                
                return result
            
            # 执行合并
            merged_df = merged_df.groupby(['客户简称', '规格型号', '预完工日期']).apply(merge_rows).reset_index(drop=True)
            
            # 添加所需的字段
            for col in ['备注', '产品归类', '独立包装上限']:
                if col not in merged_df.columns:
                    merged_df[col] = ''
            
            logger.info(f"任务数据合并完成，从 {len(df)} 行合并到 {len(merged_df)} 行")
            print(f"  任务数据合并完成，从 {len(df)} 行合并到 {len(merged_df)} 行")
            
            return merged_df
            
        except Exception as e:
            logger.error(f"合并任务数据失败: {str(e)}")
            # 如果合并失败，至少添加所需的字段
            for col in ['备注', '产品归类', '独立包装上限']:
                if col not in df.columns:
                    df[col] = ''
            return df
    
    def _build_packaging_library(self, history_data, task_data):
        """
        建立产品包装方案库
        """
        try:
            packaging_library = {}
            
            # 检查必要的列
            if '规格型号' not in task_data.columns or '规格型号' not in history_data.columns:
                logger.warning("缺少规格型号列，无法建立产品包装方案库")
                print("  缺少规格型号列，无法建立产品包装方案库")
                return packaging_library, task_data
            
            # 获取所有任务数据中的产品规格型号
            task_product_types = set(task_data['规格型号'].dropna().unique())
            
            # 为每个产品建立包装方案库
            for product_type in task_product_types:
                # 在历史包装数据中查找该产品
                product_history = history_data[history_data['规格型号'] == product_type].copy()
                
                if len(product_history) == 0:
                    # 如果没有包装方案，标记为设计项
                    logger.info(f"产品 {product_type} 没有对应的包装方案，标记为设计项")
                    task_data.loc[task_data['规格型号'] == product_type, '产品归类'] = '无数据'
                else:
                    # 按包装类型分组
                    packaging_library[product_type] = {
                        '独立包装方案库': product_history[product_history['包装类型'] == '独立包装'].copy(),
                        '组合包装方案库': product_history[product_history['包装类型'] == '组合包装'].copy()
                    }
                    
                    # 独立包装上限判定
                    if len(packaging_library[product_type]['独立包装方案库']) > 0:
                        # 检查是否存在相同客户和日期的多个独立包装方案
                        if all(col in packaging_library[product_type]['独立包装方案库'].columns for col in ['客户简称', '预完工日期']):
                            grouped = packaging_library[product_type]['独立包装方案库'].groupby(['客户简称', '预完工日期'])
                            has_limit = any(len(group) > 1 for _, group in grouped)
                            limit_value = '有' if has_limit else '无'
                            task_data.loc[task_data['规格型号'] == product_type, '独立包装上限'] = limit_value
                    
                    # 数据清理：如果存在相同规格型号和数量的多个包装方案，保留日期最晚的
                    for package_type in ['独立包装方案库', '组合包装方案库']:
                        if len(packaging_library[product_type][package_type]) > 0 and \
                           all(col in packaging_library[product_type][package_type].columns for col in ['规格型号', '数量', '预完工日期']):
                            # 按规格型号和数量分组，保留日期最晚的
                            latest_by_group = packaging_library[product_type][package_type].sort_values('预完工日期', ascending=False)
                            latest_by_group = latest_by_group.drop_duplicates(['规格型号', '数量'], keep='first')
                            packaging_library[product_type][package_type] = latest_by_group
            
            logger.info(f"产品包装方案库建立完成，包含 {len(packaging_library)} 种产品")
            print(f"  产品包装方案库建立完成，包含 {len(packaging_library)} 种产品")
            
            return packaging_library, task_data
            
        except Exception as e:
            logger.error(f"建立产品包装方案库失败: {str(e)}")
            return packaging_library, task_data
    
    def _classify_products(self, task_data, packaging_library):
        """
        产品归类
        """
        try:
            # 检查必要的列
            if '规格型号' not in task_data.columns:
                logger.warning("缺少规格型号列，无法进行产品归类")
                print("  缺少规格型号列，无法进行产品归类")
                return task_data
            
            # 为每个产品进行归类
            for product_type, libraries in packaging_library.items():
                # 检查各包装类型方案库的情况
                has_individual = len(libraries['独立包装方案库']) > 0
                has_combined = len(libraries['组合包装方案库']) > 0
                
                if has_individual and has_combined:
                    product_class = '混合包装'
                elif has_individual:
                    product_class = '仅独立包装'
                elif has_combined:
                    product_class = '仅组合包装'
                else:
                    product_class = '无数据'
                
                # 更新产品归类
                task_data.loc[task_data['规格型号'] == product_type, '产品归类'] = product_class
                
                logger.info(f"产品 {product_type} 归类为: {product_class}")
            
            # 统计归类结果
            classification_counts = task_data['产品归类'].value_counts()
            for category, count in classification_counts.items():
                print(f"  {category}: {count} 条")
            
            return task_data
            
        except Exception as e:
            logger.error(f"产品归类失败: {str(e)}")
            return task_data
    
    # =============== 3. 任务分类与数据匹配阶段 ===============
    def task_classification_matching_phase(self):
        """
        任务分类与数据匹配阶段：任务分类处理、数据匹配、数据校验与修正
        """
        print("\n=== 3. 任务分类与数据匹配阶段 ===")
        logger.info("开始任务分类与数据匹配阶段")
        
        try:
            # 检查预处理阶段数据是否存在
            if 'preprocessing' not in self.processed_data:
                print("✗ 请先完成数据预处理阶段")
                return False
            
            # 获取预处理阶段的数据
            task_data = self.processed_data['preprocessing']['task_data'].copy()
            packaging_library = self.processed_data['preprocessing']['packaging_library']
            
            # 3.1 任务分类处理
            print("\n3.1 任务分类处理")
            task_data = self._classify_tasks(task_data, packaging_library)
            
            # 3.2 数据匹配
            print("\n3.2 数据匹配")
            task_data = self._match_data(task_data, packaging_library)
            
            # 3.3 数据校验与修正
            print("\n3.3 数据校验与修正")
            validation_results = self._validate_and_correct(task_data)
            
            # 保存任务分类与匹配阶段结果
            self.processed_data['classification_matching'] = {
                'task_data': task_data.copy(),
                'validation_results': validation_results
            }
            
            # 生成任务分类与匹配阶段可视化
            self._generate_classification_matching_visualization(task_data, validation_results)
            
            # 保存任务分类与匹配阶段数据
            self._save_processed_data(task_data, "3_1_任务数据_分类匹配.csv")
            
            logger.info("任务分类与数据匹配阶段完成")
            print("\n✓ 任务分类与数据匹配阶段完成")
            return True
            
        except Exception as e:
            logger.error(f"任务分类与数据匹配阶段失败: {str(e)}")
            print(f"✗ 任务分类与数据匹配阶段失败: {str(e)}")
            return False
    
    def _classify_tasks(self, df, packaging_library):
        """
        任务分类处理
        """
        try:
            classified_df = df.copy()
            new_rows = []  # 用于存储拆分后的新行
            rows_to_drop = []  # 用于存储需要删除的原始行
            
            # 确保备注字段存在
            if '备注' not in classified_df.columns:
                classified_df['备注'] = ''
            
            # 获取当前最大行号
            current_max_line = 0
            if not classified_df.empty and '行号' in classified_df.columns:
                try:
                    # 处理可能的字符串行号
                    line_numbers = []
                    for line in classified_df['行号']:
                        try:
                            # 尝试提取数字部分
                            num_str = ''.join(filter(str.isdigit, str(line)))
                            if num_str:
                                line_numbers.append(int(num_str))
                        except (ValueError, TypeError):
                            continue
                    if line_numbers:
                        current_max_line = max(line_numbers)
                except Exception:
                    current_max_line = 0
            
            # 遍历每一行任务数据
            for idx, row in classified_df.iterrows():
                product_type = row.get('规格型号')
                product_class = row.get('产品归类')
                quantity = row.get('数量', 0)
                
                # 步骤1: 判断产品归类
                if product_class == '无数据':
                    # 无数据，标记为设计项
                    classified_df.at[idx, '备注'] = '设计项'
                    continue
                elif product_class == '仅组合包装':
                    # 仅组合包装，标记为参考项
                    classified_df.at[idx, '备注'] = '参考项（仅组合包装）'
                    continue
                
                # 其他情况：仅独立包装或混合包装
                # 检查产品是否在包装方案库中
                if product_type in packaging_library:
                    # 获取独立包装方案库
                    individual_packages = packaging_library[product_type]['独立包装方案库']
                    
                    if len(individual_packages) > 0 and '数量' in individual_packages.columns:
                        # 步骤2: 独立包装数量精确匹配
                        exact_matches = individual_packages[individual_packages['数量'] == quantity]
                        if len(exact_matches) > 0:
                            # 找到完全匹配，标记为复用项
                            classified_df.at[idx, '备注'] = '复用项'
                            continue
                        
                        # 步骤3: 数量关系分析
                        min_quantity = individual_packages['数量'].min()
                        if quantity < min_quantity:
                            # 数量小于最小值，标记为参考项
                            classified_df.at[idx, '备注'] = f'参考项（{product_class}）'
                            continue
                        
                        # 步骤4: 检查独立包装上限
                        if row.get('独立包装上限') == '有':
                            # 有独立包装上限，进入步骤5: 迭代拆分及任务数据处理
                            # 获取可用的包装数量列表
                            quantity_values = individual_packages['数量'].tolist()
                            logger.info(f"对产品{product_type}进行迭代拆分，数量={quantity}，可用包装数量={quantity_values}")
                            
                            # 优化拆分算法
                            split_result = self._optimize_split(quantity, quantity_values)
                            logger.info(f"拆分结果: {split_result}")
                            
                            # 只有一个可用包装数量但标记为有独立包装上限时也应该拆分
                            if split_result:
                                # 记录需要删除的原始行
                                rows_to_drop.append(idx)
                                
                                # 计算使用的数量总和
                                used_quantity = sum(split_result)
                                remaining_quantity = quantity - used_quantity
                                
                                # 添加拆分后的行（包括剩余数量）
                                split_index = 1  # 拆分序号，从1开始递增
                                
                                # 先处理拆分结果中的包装数量
                                for qty in split_result:
                                    # 找到对应的包装方案
                                    package_info = None
                                    matches = individual_packages[individual_packages['数量'] == qty]
                                    if len(matches) > 0:
                                        package_info = matches.iloc[0]
                                    
                                    # 创建新行
                                    new_row = row.copy()
                                    new_row['数量'] = qty
                                    new_row['备注'] = '复用项'
                                    
                                    # 为拆分后的行添加原始行号和递增序号，格式为"原始行号-序号"
                                    original_line_number = row.get('行号')
                                    new_row['行号'] = f"{original_line_number}-{split_index}"
                                    split_index += 1  # 拆分序号递增
                                    
                                    # 添加包装方案信息（如果有）
                                    if package_info is not None:
                                        for col in ['包装箱方案', '包装箱外口尺寸', '重量']:
                                            if col in package_info.index:
                                                new_row[col] = package_info[col]
                                    
                                    new_rows.append(new_row)
                                
                                # 处理剩余数量（如果有）
                                if remaining_quantity > 0:
                                    # 创建剩余数量的行
                                    new_row = row.copy()
                                    new_row['数量'] = remaining_quantity
                                    new_row['备注'] = f'参考项（{product_class}）'
                                    
                                    # 为剩余数量行添加原始行号和递增序号
                                    original_line_number = row.get('行号')
                                    new_row['行号'] = f"{original_line_number}-{split_index}"
                                    
                                    new_rows.append(new_row)
                        else:
                            # 无独立包装上限，标记为参考项
                            classified_df.at[idx, '备注'] = f'参考项（{product_class}）'
                    else:
                        # 没有独立包装方案
                        classified_df.at[idx, '备注'] = f'参考项（{product_class}）'
                else:
                    # 产品不在包装方案库中
                    classified_df.at[idx, '备注'] = '设计项'
            
            # 删除需要拆分的原始行
            if rows_to_drop:
                classified_df = classified_df.drop(rows_to_drop)
            
            # 添加拆分后的新行
            if new_rows:
                new_df = pd.DataFrame(new_rows)
                classified_df = pd.concat([classified_df, new_df], ignore_index=True)
            
            # 统计分类结果
            task_class_counts = classified_df['备注'].value_counts()
            for task_class, count in task_class_counts.items():
                print(f"  {task_class}: {count} 条")
            
            return classified_df
            
        except Exception as e:
            logger.error(f"任务分类失败: {str(e)}")
            return df
    
    def _format_delivery_date(self, date_str):
        """
        格式化预完工日期，提取出正确的日期部分
        例如：从"2025/5/23(6-24)"中提取出"2025/5/23"
        
        Args:
            date_str: 日期字符串
            
        Returns:
            str: 格式化后的日期字符串
        """
        if pd.isna(date_str):
            return date_str
        
        # 转换为字符串
        date_str = str(date_str).strip()
        
        # 检查是否包含括号
        if '(' in date_str:
            # 提取括号前的部分
            date_str = date_str.split('(')[0].strip()
        
        return date_str
        """
        优化拆分算法
        
        Args:
            total_quantity: 需要拆分的总数量
            available_quantities: 可用的包装数量列表
            
        Returns:
            list: 拆分结果列表，当可以完全拆分或剩余数量小于最小包装数量时返回有效结果
        """
        # 去重并按降序排序可用数量
        sorted_quantities = sorted(set(available_quantities), reverse=True)
        if not sorted_quantities:
            return None
            
        min_package = min(sorted_quantities)
        
        # 首先尝试精确拆分
        exact_result = self._find_exact_split(total_quantity, sorted_quantities)
        if exact_result:
            return sorted(exact_result, reverse=True)
        
        # 如果无法精确拆分，尝试找到剩余数量小于最小值的最佳方案
        best_result = None
        best_score = float('-inf')
        
        # 使用回溯法尝试所有可能的组合
        def backtrack(remaining, current_split, index):
            nonlocal best_result, best_score
            
            # 如果剩余量为0，找到精确拆分
            if remaining == 0:
                # 评估结果
                score = self._evaluate_split(current_split)
                if score > best_score:
                    best_score = score
                    best_result = current_split.copy()
                return
            
            # 如果剩余量小于最小包装，也是可接受的解决方案
            if remaining < min_package:
                # 评估结果
                score = self._evaluate_split(current_split)
                if score > best_score:
                    best_score = score
                    best_result = current_split.copy()
                return
            
            # 如果剩余量大于最大可用包装，尝试使用最大的包装
            for i in range(index, len(sorted_quantities)):
                qty = sorted_quantities[i]
                if qty <= remaining:
                    current_split.append(qty)
                    # 递归继续拆分剩余量
                    backtrack(remaining - qty, current_split, i)  # 允许重复使用相同的包装
                    current_split.pop()
        
        backtrack(total_quantity, [], 0)
        
        # 如果找到了最佳方案，按降序返回
        if best_result:
            return sorted(best_result, reverse=True)
        
        # 如果没有找到合适的拆分方案，返回None
        return None
    
    def _find_exact_split(self, total_quantity, sorted_quantities):
        """
        查找精确拆分方案
        """
        # 贪心算法尝试精确拆分
        remaining = total_quantity
        result = []
        
        for qty in sorted_quantities:
            if qty <= remaining:
                count = remaining // qty
                result.extend([qty] * count)
                remaining -= qty * count
                
                if remaining == 0:
                    break
        
        if remaining == 0:
            return result
        return None
    
    def _evaluate_split(self, split_result):
        """
        评估拆分结果的得分
        根据项目大纲的优先级：
        1. 包装数量尽量少（即拆分后的任务数据行数越少越好）
        2. 包装箱种类尽量少（即拆分后的任务数据"包装箱方案"字段值种类越少越好）
        """
        if not split_result:
            return 0
        
        # 得分1：包装数量（越少越好）
        score1 = 100 / len(split_result)  # 包装数量倒数
        
        # 得分2：包装箱种类（越少越好）
        unique_count = len(set(split_result))
        score2 = 50 / unique_count  # 种类数量倒数
        
        # 总分
        total_score = score1 + score2
        return total_score
    
    def _optimize_split(self, total_quantity, available_quantities):
        """优化拆分算法
        
        Args:
            total_quantity: 需要拆分的总数量
            available_quantities: 可用的包装数量列表
            
        Returns:
            list: 拆分结果列表，当可以完全拆分或剩余数量小于最小包装数量时返回有效结果
        """
        # 去重并按降序排序可用数量
        sorted_quantities = sorted(set(available_quantities), reverse=True)
        if not sorted_quantities:
            return None
            
        min_package = min(sorted_quantities)
        best_result = None
        best_score = float('-inf')
        
        # 首先尝试精确拆分
        exact_result = self._find_exact_split(total_quantity, sorted_quantities)
        if exact_result:
            return sorted(exact_result, reverse=True)
        
        # 如果无法精确拆分，尝试找到剩余数量小于最小值的最佳方案
        # 使用回溯法尝试所有可能的组合
        def backtrack(remaining, current_split, start_index):
            nonlocal best_result, best_score
            
            # 检查当前拆分是否符合要求（剩余数量小于最小值）
            if remaining >= 0 and remaining < min_package:
                # 计算得分：优先考虑剩余数量少，其次考虑使用的包装种类少
                score = -remaining - len(set(current_split)) * 0.1
                if score > best_score:
                    best_score = score
                    best_result = current_split.copy()
                return
            
            # 尝试每种可能的包装数量，支持重复使用（start_index不增加，允许重复使用当前包装数量）
            for i in range(start_index, len(sorted_quantities)):
                qty = sorted_quantities[i]
                if qty > remaining:
                    continue
                    
                max_count = remaining // qty
                for count in range(max_count, 0, -1):
                    # 添加当前包装数量
                    new_split = current_split + [qty] * count
                    # 继续尝试当前或下一个包装数量（支持重复使用）
                    backtrack(remaining - qty * count, new_split, i)
        
        # 开始回溯搜索
        backtrack(total_quantity, [], 0)
        
        # 对找到的最佳结果进行排序
        if best_result:
            return sorted(best_result, reverse=True)
        
        return None
    
    def _find_exact_split(self, target, options, path=None):
        """递归查找精确拆分方案"""
        if path is None:
            path = []
        
        # 基本情况：已找到精确拆分
        if target == 0:
            return path
        
        # 尝试每种可能的包装数量，支持重复使用同一个包装数量
        for option in options:
            if option > target:
                continue
                
            # 计算可以使用多少个当前包装
            max_count = target // option
            for count in range(max_count, 0, -1):
                new_target = target - (option * count)
                new_path = path + [option] * count
                
                # 移除对选项的过滤，允许重复使用同一个包装数量
                result = self._find_exact_split(new_target, options, new_path)
                if result:
                    return result
        
        return None
    
    def _match_data(self, df, packaging_library):
        """
        数据匹配
        """
        try:
            matched_df = df.copy()
            
            # 遍历每一行任务数据
            for idx, row in matched_df.iterrows():
                task_class = row.get('备注')
                product_type = row.get('规格型号')
                quantity = row.get('数量', 0)
                
                # 根据任务分类进行匹配
                if task_class == '复用项' and product_type in packaging_library:
                    # 复用项：直接引用包装方案
                    individual_packages = packaging_library[product_type]['独立包装方案库']
                    if len(individual_packages) > 0:
                        # 找到数量匹配的方案
                        match = individual_packages[individual_packages['数量'] == quantity]
                        if len(match) > 0:
                            # 复制包装方案信息
                            for col in ['包装箱外口尺寸', '重量', '包装箱方案']:
                                if col in match.columns:
                                    matched_df.at[idx, col] = match.iloc[0][col]
                elif task_class.startswith('参考项'):
                    # 参考项处理（简化实现）
                    # 可以根据不同的参考项类型进行更复杂的处理
                    pass
                # 设计项不需要匹配
            
            # 数据排序分组
            # 按照备注字段排序
            note_order = {'复用项': 0, '复用项（拆分）': 1, '设计项': 2, '参考项（仅独立包装）': 3, 
                         '参考项（混合包装）': 4, '参考项（仅组合包装）': 5}
            
            # 添加排序键
            matched_df['_sort_key'] = matched_df['备注'].map(lambda x: note_order.get(x, 99))
            
            # 排序
            sort_columns = ['_sort_key']
            if '客户简称' in matched_df.columns:
                sort_columns.append('客户简称')
            if '预完工日期' in matched_df.columns:
                sort_columns.append('预完工日期')
            if '规格型号' in matched_df.columns:
                sort_columns.append('规格型号')
            
            matched_df = matched_df.sort_values(sort_columns)
            
            # 删除临时排序键
            if '_sort_key' in matched_df.columns:
                matched_df = matched_df.drop('_sort_key', axis=1)
            
            logger.info("数据匹配完成")
            print("  数据匹配完成")
            
            return matched_df
            
        except Exception as e:
            logger.error(f"数据匹配失败: {str(e)}")
            return df
    
    def _validate_and_correct(self, df):
        """
        数据校验与修正
        """
        try:
            validation_results = {
                'total_rows': len(df),
                'issues': []
            }
            
            # 统计任务数据中的产品种类
            if '规格型号' in df.columns:
                product_types = df['规格型号'].nunique()
                validation_results['product_types'] = product_types
                print(f"  任务数据中共有 {product_types} 种不同的产品规格型号")
            
            # 统计同客户同日期同产品的订单数量
            if all(col in df.columns for col in ['客户简称', '规格型号', '预完工日期', '数量']):
                grouped = df.groupby(['客户简称', '规格型号', '预完工日期'])['数量'].sum().reset_index()
                validation_results['order_groups'] = len(grouped)
                print(f"  任务数据中有 {len(grouped)} 组不同的客户-产品-日期组合")
            
            # 检查数据一致性
            # 示例：检查数量是否为正数
            if '数量' in df.columns:
                negative_quantity = df[df['数量'] <= 0]
                if len(negative_quantity) > 0:
                    issue = {
                        'type': '数量异常',
                        'description': f'发现 {len(negative_quantity)} 行数量异常数据',
                        'count': len(negative_quantity)
                    }
                    validation_results['issues'].append(issue)
                    print(f"  ⚠️  发现 {len(negative_quantity)} 行数量异常数据")
            
            # 检查必填字段
            for col in ['客户简称', '规格型号', '预完工日期']:
                if col in df.columns:
                    missing_values = df[df[col].isna() | (df[col] == '')]
                    if len(missing_values) > 0:
                        issue = {
                            'type': '字段缺失',
                            'description': f'发现 {len(missing_values)} 行缺少{col}数据',
                            'count': len(missing_values)
                        }
                        validation_results['issues'].append(issue)
                        print(f"  ⚠️  发现 {len(missing_values)} 行缺少{col}数据")
            
            logger.info(f"数据校验完成，发现 {len(validation_results['issues'])} 个问题")
            
            return validation_results
            
        except Exception as e:
            logger.error(f"数据校验失败: {str(e)}")
            return {'total_rows': len(df), 'issues': [{'type': '校验错误', 'description': str(e)}]}
    
    # =============== 4. 数据后处理阶段 ===============
    def data_postprocessing_phase(self):
        """
        数据后处理阶段：数据输出与格式化
        """
        print("\n=== 4. 数据后处理阶段 ===")
        logger.info("开始数据后处理阶段")
        
        try:
            # 检查任务分类与匹配阶段数据是否存在
            if 'classification_matching' not in self.processed_data:
                print("✗ 请先完成任务分类与数据匹配阶段")
                return False
            
            # 获取任务分类与匹配阶段的数据
            task_data = self.processed_data['classification_matching']['task_data'].copy()
            
            # 4.1 数据输出与格式化
            print("\n4.1 数据输出与格式化")
            formatted_data = self._format_output_data(task_data)
            
            # 保存后处理阶段结果
            self.processed_data['postprocessing'] = {
                'formatted_data': formatted_data.copy()
            }
            
            # 生成后处理阶段可视化
            self._generate_postprocessing_visualization(formatted_data)
            
            # 保存后处理阶段数据
            self._save_processed_data(formatted_data, "4_1_任务数据_最终结果.csv")
            
            logger.info("数据后处理阶段完成")
            print("\n✓ 数据后处理阶段完成")
            return True
            
        except Exception as e:
            logger.error(f"数据后处理阶段失败: {str(e)}")
            print(f"✗ 数据后处理阶段失败: {str(e)}")
            return False
    
    def _format_output_data(self, df):
        """
        格式化输出数据
        """
        try:
            formatted_df = df.copy()
            
            # 确保所有必要的列都存在
            required_columns = ['客户简称', '规格型号', '预完工日期', '数量', '备注', '产品归类', '独立包装上限']
            for col in required_columns:
                if col not in formatted_df.columns:
                    formatted_df[col] = ''
            
            # 填充缺失值
            numeric_columns = formatted_df.select_dtypes(include=[np.number]).columns
            formatted_df[numeric_columns] = formatted_df[numeric_columns].fillna(0)
            
            string_columns = formatted_df.select_dtypes(include=['object']).columns
            formatted_df[string_columns] = formatted_df[string_columns].fillna('')
            
            # 去除字符串前后空格
            for col in string_columns:
                formatted_df[col] = formatted_df[col].astype(str).str.strip()
            
            # 处理预完工日期格式
            if '预完工日期' in formatted_df.columns:
                # 先使用自定义函数提取括号前的日期部分
                formatted_df['预完工日期'] = formatted_df['预完工日期'].apply(self._format_delivery_date)
                # 然后尝试标准化日期格式为'YYYY-MM-DD'
                try:
                    # 使用errors='coerce'确保无法解析的日期会被转换为NaT
                    formatted_df['预完工日期'] = pd.to_datetime(formatted_df['预完工日期'], errors='coerce').dt.strftime('%Y-%m-%d')
                    # 对于无法解析的日期，保持原始格式
                    formatted_df['预完工日期'] = formatted_df['预完工日期'].fillna('')
                except Exception as e:
                    logger.warning(f"无法标准化日期格式: {str(e)}")
            
            logger.info("数据格式化完成")
            print("  数据格式化完成")
            
            return formatted_df
            
        except Exception as e:
            logger.error(f"格式化输出数据失败: {str(e)}")
            return df
    
    # =============== 可视化生成函数 ===============
    def _generate_pre_processing_visualization(self, task_stats):
        """
        生成前处理阶段可视化
        """
        try:
            # 设置中文字体支持
            plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
            plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
            
            # 创建前处理阶段可视化目录
            viz_subdir = os.path.join(VISUALIZATIONS_DIR, '1_前处理阶段')
            self.ensure_directory(viz_subdir)
            
            # 生成产品类型分布饼图
            if 'order_summary' in task_stats and len(task_stats['order_summary']) > 0:
                # 按规格型号统计数量
                product_summary = task_stats['order_summary'].groupby('规格型号')['数量'].sum().reset_index()
                product_summary = product_summary.sort_values('数量', ascending=False)
                
                # 只显示前10个产品
                if len(product_summary) > 10:
                    top_products = product_summary.head(10)
                    others_sum = product_summary.iloc[10:]['数量'].sum()
                    top_products.loc[len(top_products)] = ['其他', others_sum]
                else:
                    top_products = product_summary
                
                plt.figure(figsize=(12, 8))
                plt.pie(top_products['数量'], labels=top_products['规格型号'], autopct='%1.1f%%', 
                        shadow=True, startangle=90)
                plt.axis('equal')
                plt.title('任务数据产品分布饼图（前10名）')
                
                pie_chart_path = os.path.join(viz_subdir, '1_1_产品分布饼图.png')
                plt.savefig(pie_chart_path, dpi=300, bbox_inches='tight')
                plt.close()
                
                print(f"  ✓ 生成产品分布饼图: {os.path.basename(pie_chart_path)}")
            
            # 生成客户分布柱状图
            if 'order_summary' in task_stats and len(task_stats['order_summary']) > 0:
                # 按客户统计订单数
                customer_summary = task_stats['order_summary'].groupby('客户简称').size().reset_index(name='订单数')
                customer_summary = customer_summary.sort_values('订单数', ascending=False)
                
                plt.figure(figsize=(12, 8))
                bars = plt.bar(customer_summary['客户简称'], customer_summary['订单数'])
                plt.xlabel('客户简称')
                plt.ylabel('订单数')
                plt.title('客户订单数量分布')
                plt.xticks(rotation=45, ha='right')
                
                # 添加数据标签
                for bar in bars:
                    height = bar.get_height()
                    plt.text(bar.get_x() + bar.get_width()/2., height + 0.1,
                            f'{int(height)}', ha='center', va='bottom')
                
                plt.tight_layout()
                bar_chart_path = os.path.join(viz_subdir, '1_2_客户订单分布.png')
                plt.savefig(bar_chart_path, dpi=300, bbox_inches='tight')
                plt.close()
                
                print(f"  ✓ 生成客户订单分布柱状图: {os.path.basename(bar_chart_path)}")
            
            # 生成统计数据表格
            stats_df = pd.DataFrame({
                '统计项': ['总数据行数', '产品种类数', '订单组合数'],
                '数值': [task_stats.get('total_rows', 0), 
                         task_stats.get('product_types', 0), 
                         task_stats.get('order_groups', 0)]
            })
            
            stats_path = os.path.join(viz_subdir, '1_3_前处理统计数据.csv')
            stats_df.to_csv(stats_path, index=False, encoding='utf-8-sig')
            
            print(f"  ✓ 生成前处理统计数据表格: {os.path.basename(stats_path)}")
            
        except Exception as e:
            logger.error(f"生成前处理阶段可视化失败: {str(e)}")
            print(f"  ✗ 生成前处理阶段可视化失败: {str(e)}")
    
    def _generate_preprocessing_visualization(self, history_data, task_data):
        """
        生成预处理阶段可视化
        """
        try:
            # 设置中文字体支持
            plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
            plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
            
            # 创建预处理阶段可视化目录
            viz_subdir = os.path.join(VISUALIZATIONS_DIR, '2_预处理阶段')
            self.ensure_directory(viz_subdir)
            
            # 生成包装类型分布饼图
            if '包装类型' in history_data.columns:
                packaging_counts = history_data['包装类型'].value_counts()
                
                plt.figure(figsize=(10, 6))
                plt.pie(packaging_counts.values, labels=packaging_counts.index, 
                        autopct='%1.1f%%', shadow=True, startangle=90)
                plt.axis('equal')
                plt.title('历史数据包装类型分布')
                
                pie_chart_path = os.path.join(viz_subdir, '2_1_包装类型分布.png')
                plt.savefig(pie_chart_path, dpi=300, bbox_inches='tight')
                plt.close()
                
                print(f"  ✓ 生成包装类型分布饼图: {os.path.basename(pie_chart_path)}")
            
            # 生成产品归类分布柱状图
            if '产品归类' in task_data.columns:
                classification_counts = task_data['产品归类'].value_counts()
                
                plt.figure(figsize=(10, 6))
                bars = plt.bar(classification_counts.index, classification_counts.values)
                plt.xlabel('产品归类')
                plt.ylabel('数量')
                plt.title('产品归类分布')
                plt.xticks(rotation=45)
                
                # 添加数据标签
                for bar in bars:
                    height = bar.get_height()
                    plt.text(bar.get_x() + bar.get_width()/2., height + 0.1,
                            f'{int(height)}', ha='center', va='bottom')
                
                plt.tight_layout()
                bar_chart_path = os.path.join(viz_subdir, '2_2_产品归类分布.png')
                plt.savefig(bar_chart_path, dpi=300, bbox_inches='tight')
                plt.close()
                
                print(f"  ✓ 生成产品归类分布柱状图: {os.path.basename(bar_chart_path)}")
            
            # 生成独立包装上限分布
            if '独立包装上限' in task_data.columns:
                limit_counts = task_data['独立包装上限'].value_counts()
                
                plt.figure(figsize=(8, 6))
                bars = plt.bar(limit_counts.index, limit_counts.values)
                plt.xlabel('独立包装上限')
                plt.ylabel('数量')
                plt.title('独立包装上限分布')
                
                # 添加数据标签
                for bar in bars:
                    height = bar.get_height()
                    plt.text(bar.get_x() + bar.get_width()/2., height + 0.1,
                            f'{int(height)}', ha='center', va='bottom')
                
                plt.tight_layout()
                limit_chart_path = os.path.join(viz_subdir, '2_3_独立包装上限分布.png')
                plt.savefig(limit_chart_path, dpi=300, bbox_inches='tight')
                plt.close()
                
                print(f"  ✓ 生成独立包装上限分布柱状图: {os.path.basename(limit_chart_path)}")
            
            # 生成统计数据表格
            stats_data = []
            if '包装类型' in history_data.columns:
                packaging_stats = history_data['包装类型'].value_counts().to_dict()
                for p_type, count in packaging_stats.items():
                    stats_data.append({'统计项': f'历史{str(p_type)}', '数值': count})
            
            if '产品归类' in task_data.columns:
                class_stats = task_data['产品归类'].value_counts().to_dict()
                for c_type, count in class_stats.items():
                    stats_data.append({'统计项': f'产品{str(c_type)}', '数值': count})
            
            stats_df = pd.DataFrame(stats_data)
            stats_path = os.path.join(viz_subdir, '2_4_预处理统计数据.csv')
            stats_df.to_csv(stats_path, index=False, encoding='utf-8-sig')
            
            print(f"  ✓ 生成预处理统计数据表格: {os.path.basename(stats_path)}")
            
        except Exception as e:
            logger.error(f"生成预处理阶段可视化失败: {str(e)}")
            print(f"  ✗ 生成预处理阶段可视化失败: {str(e)}")
    
    def _generate_classification_matching_visualization(self, task_data, validation_results):
        """
        生成任务分类与匹配阶段可视化
        """
        try:
            # 设置中文字体支持
            plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
            plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
            
            # 创建任务分类与匹配阶段可视化目录
            viz_subdir = os.path.join(VISUALIZATIONS_DIR, '3_分类匹配阶段')
            self.ensure_directory(viz_subdir)
            
            # 生成任务分类分布饼图
            if '备注' in task_data.columns:
                task_class_counts = task_data['备注'].value_counts()
                
                plt.figure(figsize=(10, 6))
                plt.pie(task_class_counts.values, labels=task_class_counts.index, 
                        autopct='%1.1f%%', shadow=True, startangle=90)
                plt.axis('equal')
                plt.title('任务分类分布')
                
                pie_chart_path = os.path.join(viz_subdir, '3_1_任务分类分布.png')
                plt.savefig(pie_chart_path, dpi=300, bbox_inches='tight')
                plt.close()
                
                print(f"  ✓ 生成任务分类分布饼图: {os.path.basename(pie_chart_path)}")
            
            # 生成校验问题柱状图
            if validation_results and 'issues' in validation_results and len(validation_results['issues']) > 0:
                issues_df = pd.DataFrame(validation_results['issues'])
                
                plt.figure(figsize=(10, 6))
                bars = plt.bar(issues_df['type'], issues_df['count'])
                plt.xlabel('问题类型')
                plt.ylabel('数量')
                plt.title('数据校验问题统计')
                plt.xticks(rotation=45)
                
                # 添加数据标签
                for bar in bars:
                    height = bar.get_height()
                    plt.text(bar.get_x() + bar.get_width()/2., height + 0.1,
                            f'{int(height)}', ha='center', va='bottom')
                
                plt.tight_layout()
                issues_chart_path = os.path.join(viz_subdir, '3_2_校验问题统计.png')
                plt.savefig(issues_chart_path, dpi=300, bbox_inches='tight')
                plt.close()
                
                print(f"  ✓ 生成校验问题统计柱状图: {os.path.basename(issues_chart_path)}")
            
            # 生成统计数据表格
            stats_data = []
            stats_data.append({'统计项': '总任务数', '数值': validation_results.get('total_rows', 0)})
            stats_data.append({'统计项': '产品种类数', '数值': validation_results.get('product_types', 0)})
            stats_data.append({'统计项': '订单组合数', '数值': validation_results.get('order_groups', 0)})
            stats_data.append({'统计项': '校验问题数', '数值': len(validation_results.get('issues', []))})
            
            if '备注' in task_data.columns:
                task_class_stats = task_data['备注'].value_counts().to_dict()
                for t_type, count in task_class_stats.items():
                    stats_data.append({'统计项': f'{str(t_type)}', '数值': count})
            
            stats_df = pd.DataFrame(stats_data)
            stats_path = os.path.join(viz_subdir, '3_3_分类匹配统计数据.csv')
            stats_df.to_csv(stats_path, index=False, encoding='utf-8-sig')
            
            print(f"  ✓ 生成分类匹配统计数据表格: {os.path.basename(stats_path)}")
            
        except Exception as e:
            logger.error(f"生成任务分类与匹配阶段可视化失败: {str(e)}")
            print(f"  ✗ 生成任务分类与匹配阶段可视化失败: {str(e)}")
    
    def _generate_postprocessing_visualization(self, formatted_data):
        """
        生成后处理阶段可视化
        """
        try:
            # 设置中文字体支持
            plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
            plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
            
            # 创建后处理阶段可视化目录
            viz_subdir = os.path.join(VISUALIZATIONS_DIR, '4_后处理阶段')
            self.ensure_directory(viz_subdir)
            
            # 生成最终任务分类分布饼图
            if '备注' in formatted_data.columns:
                task_class_counts = formatted_data['备注'].value_counts()
                
                plt.figure(figsize=(10, 6))
                plt.pie(task_class_counts.values, labels=task_class_counts.index, 
                        autopct='%1.1f%%', shadow=True, startangle=90)
                plt.axis('equal')
                plt.title('最终任务分类分布')
                
                pie_chart_path = os.path.join(viz_subdir, '4_1_最终任务分类分布.png')
                plt.savefig(pie_chart_path, dpi=300, bbox_inches='tight')
                plt.close()
                
                print(f"  ✓ 生成最终任务分类分布饼图: {os.path.basename(pie_chart_path)}")
            
            # 生成客户-任务数量关系图
            if all(col in formatted_data.columns for col in ['客户简称', '数量']):
                customer_summary = formatted_data.groupby('客户简称')['数量'].sum().reset_index()
                customer_summary = customer_summary.sort_values('数量', ascending=False)
                
                plt.figure(figsize=(12, 8))
                bars = plt.bar(customer_summary['客户简称'], customer_summary['数量'])
                plt.xlabel('客户简称')
                plt.ylabel('总数量')
                plt.title('客户任务总数量分布')
                plt.xticks(rotation=45, ha='right')
                
                # 添加数据标签
                for bar in bars:
                    height = bar.get_height()
                    plt.text(bar.get_x() + bar.get_width()/2., height + 10,
                            f'{int(height)}', ha='center', va='bottom')
                
                plt.tight_layout()
                customer_chart_path = os.path.join(viz_subdir, '4_2_客户任务数量分布.png')
                plt.savefig(customer_chart_path, dpi=300, bbox_inches='tight')
                plt.close()
                
                print(f"  ✓ 生成客户任务数量分布柱状图: {os.path.basename(customer_chart_path)}")
            
            # 生成最终统计数据表格
            stats_data = []
            stats_data.append({'统计项': '最终数据行数', '数值': len(formatted_data)})
            
            if '规格型号' in formatted_data.columns:
                stats_data.append({'统计项': '最终产品种类数', '数值': formatted_data['规格型号'].nunique()})
            
            if '客户简称' in formatted_data.columns:
                stats_data.append({'统计项': '客户数量', '数值': formatted_data['客户简称'].nunique()})
            
            if '备注' in formatted_data.columns:
                task_class_stats = formatted_data['备注'].value_counts().to_dict()
                for t_type, count in task_class_stats.items():
                    stats_data.append({'统计项': f'{str(t_type)}', '数值': count})
            
            stats_df = pd.DataFrame(stats_data)
            stats_path = os.path.join(viz_subdir, '4_3_最终统计数据.csv')
            stats_df.to_csv(stats_path, index=False, encoding='utf-8-sig')
            
            print(f"  ✓ 生成最终统计数据表格: {os.path.basename(stats_path)}")
            
        except Exception as e:
            logger.error(f"生成后处理阶段可视化失败: {str(e)}")
            print(f"  ✗ 生成后处理阶段可视化失败: {str(e)}")
    
    # =============== 工具函数 ===============
    def _save_processed_data(self, df, filename):
        """
        保存处理后的数据
        分步测试脚本特有的功能：同时生成CSV和Excel两种格式
        """
        try:
            # 保存为CSV格式
            csv_file_path = os.path.join(TEST_RESULTS_DIR, filename)
            df.to_csv(csv_file_path, index=False, encoding='utf-8-sig')
            logger.info(f"成功保存CSV格式数据到: {csv_file_path}")
            
            # 额外保存为Excel格式（仅在测试脚本中实现）
            excel_filename = os.path.splitext(filename)[0] + '.xlsx'
            excel_file_path = os.path.join(TEST_RESULTS_DIR, excel_filename)
            # 使用pandas的to_excel方法，需要openpyxl引擎
            df.to_excel(excel_file_path, index=False, engine='openpyxl')
            logger.info(f"成功保存Excel格式数据到: {excel_file_path}")
            
            return True
        except Exception as e:
            logger.error(f"保存处理后的数据失败: {str(e)}")
            return False
    
    def run_full_process(self):
        """
        运行完整的数据处理流程
        """
        print("🎉 开始完整的数据处理测试流程 🎉")
        print("=" * 60)
        
        start_time = datetime.now()
        
        # 运行四个阶段
        success = True
        
        # 1. 前处理阶段
        if not self.pre_processing_phase():
            success = False
        
        # 2. 数据预处理阶段
        if success and not self.data_preprocessing_phase():
            success = False
        
        # 3. 任务分类与数据匹配阶段
        if success and not self.task_classification_matching_phase():
            success = False
        
        # 4. 数据后处理阶段
        if success and not self.data_postprocessing_phase():
            success = False
        
        end_time = datetime.now()
        duration = (end_time - start_time).total_seconds()
        
        print("\n" + "=" * 60)
        print(f"完整数据处理流程耗时: {duration:.2f} 秒")
        
        if success:
            print("\n🎉 恭喜！完整的数据处理测试流程已成功完成！")
            print(f"\n📊 处理结果文件保存在: {TEST_RESULTS_DIR}")
            print(f"📈 可视化文件保存在: {VISUALIZATIONS_DIR}")
        else:
            print("\n❌ 完整的数据处理测试流程未能全部成功完成！")
        
        return success

def main():
    """
    主函数
    """
    try:
        print("📦 数据处理测试工具 v1.0")
        print("===========================\n")
        print("按照project_outline.md中描述的四个数据处理阶段逐步实现")
        print("并为每个流程的数据处理结果生成可视化文件\n")
        
        # 创建测试器实例
        tester = DataProcessingTester()
        
        # 运行完整的数据处理流程
        success = tester.run_full_process()
        
        if success:
            print("\n✅ 测试成功！")
            return 0
        else:
            print("\n❌ 测试失败！")
            return 1
            
    except Exception as e:
        print(f"\n❌ 程序执行异常: {str(e)}")
        import traceback
        traceback.print_exc()
        return 1

if __name__ == "__main__":
    sys.exit(main())